package per.hnvcam.searchext.services.impl;

import org.apache.log4j.Logger;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.html.HtmlParser;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.xml.sax.SAXException;
import per.hnvcam.common.OrderedSet;
import per.hnvcam.common.impl.OrderedSetImpl;
import per.hnvcam.hedge.algebra.Hedge2AlgebraDefinition;
import per.hnvcam.hedge.algebra.Hedge2Quantifier;
import per.hnvcam.hedge.algebra.HedgeConstants;
import per.hnvcam.net.http.HTTPConnection;
import per.hnvcam.searchext.Constants;
import per.hnvcam.searchext.data.dao.WordDAO;
import per.hnvcam.searchext.data.model.ConditionData;
import per.hnvcam.searchext.data.model.RuleData;
import per.hnvcam.searchext.data.model.SystemConfiguration;
import per.hnvcam.searchext.data.model.Word;
import per.hnvcam.searchext.services.ClassificationService;
import per.hnvcam.searchext.services.RuleService;
import per.hnvcam.searchext.services.SpecialQuery;
import per.hnvcam.searchext.text.CommonWords;
import per.hnvcam.searchext.text.GoogleSearchHandler;
import per.hnvcam.searchext.text.WordCounter;
import per.hnvcam.searchext.text.impl.GoogleSearchHandlerImpl;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: Vi Cam
 * Date: 12/01/2012
 * Time: 11:03
 * To change this template use File | Settings | File Templates.
 */
@Service("ClassificationService")
public class ClassificationServiceImpl implements ClassificationService, InitializingBean {

   private static final Logger LOG = Logger.getLogger(ClassificationServiceImpl.class);

   @Autowired
   @Qualifier("RuleService")
   private RuleService ruleService;

   @Autowired
   @Qualifier("SpecialQuery")
   private SpecialQuery specialQuery;

   @Autowired
   @Qualifier("WordDAO")
   private WordDAO wordDAO;

   @Autowired
   @Qualifier("Hedge2AlgebraDefinition")
   private Hedge2AlgebraDefinition hedge2AlgebraDefinition;

   @Autowired
   @Qualifier("Hedge2Quantifier")
   private Hedge2Quantifier hedge2Quantifier;

   @Autowired
   private HtmlParser htmlParser;

   @Autowired
   private HTTPConnection httpConnection;

   @Autowired
   private ParseContext parseContext;

   @Autowired
   private Metadata metadata;

   @Autowired
   @Qualifier("CommonWords")
   private CommonWords commonWords;

   private List<RuleData> classifyRuleSet;


   @Override
   public void reloadParameters() {
      this.classifyRuleSet = ruleService.getClassifyRuleSet();
   }

   @Override
   public List<String> classify(URL target) throws IOException, SAXException, TikaException {
      List<String> result = new ArrayList<String>();

      SystemConfiguration systemConfiguration = specialQuery.getSystemConfiguration();

      List<ConditionData> targetCharacteristic = getCharacteristic(target);
      Map<String, Double> votedWeight = new HashMap<String, Double>();
      for (RuleData rule : classifyRuleSet) {
         double fuzzinessValue = ruleService.getCompatibilityGrade(rule, targetCharacteristic) * ruleService.getRuleWeight(rule);
         Double existingValue = votedWeight.get(rule.getClassifiedClass());
         if (existingValue == null) {
            existingValue = fuzzinessValue;
         } else {
            existingValue += fuzzinessValue;
         }
         votedWeight.put(rule.getClassifiedClass(), existingValue);
      }

      LOG.info("Classifying " + target.toString());

      TreeMap<Double, List<String>> sortedWeight = new TreeMap<Double, List<String>>();
      for (String classifiedClass : votedWeight.keySet()) {
         Double weight = votedWeight.get(classifiedClass);
         LOG.info(classifiedClass + ": " + weight);
         List<String> classes = sortedWeight.get(weight);
         if (classes == null) {
            classes = new ArrayList<String>();
         }
         classes.add(classifiedClass);
         sortedWeight.put(weight, classes);
      }

      Map.Entry<Double, List<String>> maxEntry = sortedWeight.pollLastEntry();
      Map.Entry<Double, List<String>> nextEntry = sortedWeight.pollLastEntry();
      if (maxEntry.getKey() > systemConfiguration.getWeightMinimum() && (nextEntry == null || (maxEntry.getKey() - nextEntry.getKey() > systemConfiguration.getClassifyBeliefDistance()))) {
         result.addAll(maxEntry.getValue());
      }

      return result;
   }

   private List<ConditionData> getCharacteristic(URL target) throws IOException, SAXException, TikaException {
      List<ConditionData> result = new LinkedList<ConditionData>();
      try {
         TrainingServiceImpl trainingService = new TrainingServiceImpl();
         trainingService.setCommonWords(commonWords);
         trainingService.setHtmlParser(new HtmlParser());
         trainingService.setHttpConnection(httpConnection);
         trainingService.setMetadata(metadata);
         trainingService.setParseContext(parseContext);
         trainingService.setSpecialQuery(specialQuery);
         trainingService.reset();
         trainingService.trainFromURL(target.toString());
         WordCounter wordCounter = trainingService.getWordCounter();
         for (String word : wordCounter) {
            ConditionData conditionData = new ConditionData();
            conditionData.setWord(word);
            conditionData.setFuzzyAmount(determineWordAmount(word, wordCounter.resultOf(word)));
            result.add(conditionData);
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
      return result;
   }

   private String determineWordAmount(String word, int count) {
      Word w = wordDAO.findByLabel(word);
      if (w == null) {
         return HedgeConstants.DONT_CARE;
      }
      OrderedSet<String> domain = hedge2AlgebraDefinition.getDomainOfLength(2);
      OrderedSet<Double> ranges = hedge2Quantifier.getFuzzinessInterval(2);
      if (w.getMaxCount() < domain.size()) {
         return HedgeConstants.DONT_CARE;
      }

      OrderedSet<Double> fuzzinessIntervals = multiply(ranges, w.getMaxCount());

      for (int i = 0; i < domain.size(); i++) {
         if (count > fuzzinessIntervals.getAt(i) && (count <= fuzzinessIntervals.getAt(i + 1) ||
               i == (domain.size() - 1))) {
            return domain.getAt(i);
         }
      }
      return HedgeConstants.DONT_CARE;
   }

   private OrderedSet<Double> multiply(OrderedSet<Double> item, double by) {
      OrderedSet<Double> result = new OrderedSetImpl<Double>();
      for (Double d : item) {
         result.add(d * by);
      }
      return result;
   }

   @Override
   public String getTypeName(String type) {
      SystemConfiguration systemConfiguration = specialQuery.getSystemConfiguration();
      Map<String, String> classNames = systemConfiguration.getClasses();
      String typeName = classNames.get(String.valueOf(type));
      if (typeName == null || typeName.equals("")) {
         typeName = String.valueOf(type);
      }
      return typeName;
   }

   @Override
   public Map<String, String> searchGoogleFor(String searchString) throws IOException, SAXException, TikaException {
      String searchText = searchString.replaceAll(" ", "+");
      String queryString = Constants.SEARCH_QUERY_PREFIX + searchText + Constants.SEARCH_QUERY_SUFFIX;
      InputStream inputStream = httpConnection.doGet(queryString);
      GoogleSearchHandler googleSearchHandler = new GoogleSearchHandlerImpl();
      googleSearchHandler.setSearchString(searchString);
      htmlParser.parse(inputStream, googleSearchHandler, metadata, parseContext);
      return googleSearchHandler.getSearchResults();
   }

   @Override
   public void afterPropertiesSet() throws Exception {
      reloadParameters();
   }
}
